// SPDX-FileCopyrightText: 2023-2025 CASTest Corporation Limited
// SPDX-FileCopyrightText: 2023-2025 Institute of Computing Technology, Chinese Academy of Sciences
// SPDX-License-Identifier: LGPL-v3

#include "atpg/SplitDecisionTree.h"

namespace ictest {

/**
 * @brief
 * calculate all possible fanin cubes based on current implications for justifying
 * @param driven_table
 * @param gate2scanchainid
 * @param good
 * @param bad
 * @param fault_cone
 * @param atpg_invokes
 * @param is_falling_edge_dff
 * @param no_assign
 */
void SplitJustifyNode::calc_justify(
    LogicDrivenTable *driven_table, std::map<Gate *, int> &gate2scanchainid,
    std::vector<int> &good, std::vector<int> &bad, std::vector<int> &fault_cone,
    int atpg_invokes, std::vector<int> &is_falling_edge_dff,
    std::vector<bool> &no_assign) {
  initial_ = true;
  std::vector<int> fanin_vals;
  std::vector<int> *values;
  bool good_justify;

  if (dec_type() == GOOD_JUSTIFY) {
    good_justify = true;
  } else if (dec_type() == BAD_JUSTIFY) {
    good_justify = false;
  } else {
    assert(false);
  }

  // Solve node fanin alternatives.
  for (auto fanin : gate_->FaninGates()) {
    bool fanin_is_good =
        fault_cone[fanin->GetGId()] != atpg_invokes || good_justify;
    int fanin_value =
        fanin_is_good ? good[fanin->GetGId()] : bad[fanin->GetGId()];
    fanin_vals.emplace_back(fanin_value);
  }

  justify_ =
      driven_table->possible_cubes(gate_, val_, fanin_vals, gate2scanchainid,
                                   is_falling_edge_dff, no_assign);
  max_dec_ = static_cast<int>(justify_.size());
}

/**
 * @brief
 * calculate all possible fanin cubes based on current implications for propagation
 * @param driven_table
 * logic driven table
 * @param gate2scanchainid
 * reflect from gate to scan chain id, used for identifying PPI
 * @param good good machine value
 * @param bad bad machine value
 * @param fault_cone fault cone info
 * @param atpg_invokes fault cone tag
 * @param is_falling_edge_dff
 * @param no_assign TIE_X constraints
 */
void SplitPropagationNode::calc_propagation(
    LogicDrivenTable *driven_table, std::map<Gate *, int> &gate2scanchainid,
    std::vector<int> &good, std::vector<int> &bad, std::vector<int> &fault_cone,
    int atpg_invokes, std::vector<int> &is_falling_edge_dff,
    std::vector<bool> &no_assign) {
  assert(!initial_);
  initial_ = true;
  path_justify_.resize(dec_paths_.size());

  for (int p = 0; p < dec_paths_.size(); p++) {
    int prop_idx = -1;
    auto fault_path = dec_paths_[p].first;
    auto tran_fanout = dec_paths_[p].second;

    assert(!(deter_val(good[tran_fanout->GetGId()]) &&
             deter_val(bad[tran_fanout->GetGId()])));
    assert(!(deter_val(good[tran_fanout->GetGId()]) &&
             deter_val(bad[tran_fanout->GetGId()])));

    std::vector<int> fanin_good;
    std::vector<int> fanin_bad;
    std::vector<std::vector<std::pair<Gate *, int>>> possilbe_good;
    std::vector<std::vector<std::pair<Gate *, int>>> possilbe_bad;

    for (int fi = 0; fi < tran_fanout->FaninSize(); fi++) {
      auto fanin = tran_fanout->FaninGates()[fi];
      int fanin_good_value = good[fanin->GetGId()];
      int fanin_bad_value = fault_cone[fanin->GetGId()] == atpg_invokes
                                ? bad[fanin->GetGId()]
                                : good[fanin->GetGId()];

      if (fanin == fault_path) {
        prop_idx = fi;
      }
      fanin_good.emplace_back(fanin_good_value);
      fanin_bad.emplace_back(fanin_bad_value);
    }
    if (tran_fanout->GetGType() == GType::G_DFF) {
      fanin_good.emplace_back(good[tran_fanout->GetGId()]);
      fanin_bad.emplace_back(good[tran_fanout->GetGId()]);
    }
    assert(prop_idx >= 0 && prop_idx < tran_fanout->FaninSize());

    int curr_good = LOGIC_x, curr_bad = LOGIC_x;
    if (tran_fanout->GetGType() == GType::G_DFF) {
      if (fanin_good[0] == LOGIC_x || fanin_good[1] == LOGIC_x ||
          fanin_good[2] == LOGIC_x) {
        curr_good = LOGIC_x;
      } else if (fanin_good[0] == LOGIC_1 && fanin_good[1] == LOGIC_1) {
        assert(false);
      } else if (fanin_good[0] == LOGIC_1 && fanin_good[2] == LOGIC_0) {
        curr_good = LOGIC_1;
      } else if (fanin_good[1] == LOGIC_1 && fanin_good[2] == LOGIC_0) {
        curr_good = LOGIC_0;
      } else if (fanin_good[0] == LOGIC_0 && fanin_bad[1] == LOGIC_0) {
        if (fanin_good[2] == LOGIC_1) {
          curr_good = fanin_good[3];
        } else if (fanin_good[2] == LOGIC_0) {
          curr_good = good[tran_fanout->GetGId()];
        } else {
          curr_good = LOGIC_x;
        }
      } else {
        curr_good = LOGIC_x;
      }
      if (fanin_bad[0] == LOGIC_x || fanin_bad[1] == LOGIC_x ||
          fanin_bad[2] == LOGIC_x) {
        curr_bad = LOGIC_x;
      } else if (fanin_bad[0] == LOGIC_1 && fanin_bad[1] == LOGIC_1) {
        assert(false);
      } else if (fanin_bad[0] == LOGIC_1 && fanin_bad[2] == LOGIC_0) {
        curr_bad = LOGIC_1;
      } else if (fanin_bad[1] == LOGIC_1 && fanin_bad[2] == LOGIC_0) {
        curr_bad = LOGIC_0;
      } else if (fanin_bad[0] == LOGIC_0 && fanin_bad[1] == LOGIC_0) {
        if (fanin_bad[2] == LOGIC_1) {
          curr_bad = fanin_bad[3];
        } else if (fanin_bad[2] == LOGIC_0) {
          curr_bad = good[tran_fanout->GetGId()];
        } else {
          curr_bad = LOGIC_x;
        }
      } else {
        curr_bad = LOGIC_x;
      }
    } else {
      curr_good = good[tran_fanout->GetGId()];
      curr_bad = bad[tran_fanout->GetGId()];
    }
    // use test cubes to solve all possible propagation
    // combinations at current path decision node.
    // And this procedure is so-called DETERMINE_BEST_LOGIC().
    for (int obj_val = LOGIC_S1; obj_val <= LOGIC_S0; obj_val++) {

      if ((deter_val(curr_good) && curr_good != GOOD_VALUE[obj_val]) ||
          (deter_val(curr_bad) && curr_bad != BAD_VALUE[obj_val])) {
        continue;
      }

      // Solve possible good and bad values.
      if (curr_good == LOGIC_x) {
        possilbe_good = driven_table->possible_cubes(
            tran_fanout, GOOD_VALUE[obj_val], fanin_good, gate2scanchainid,
            is_falling_edge_dff, no_assign);
      } else {
        possilbe_good.emplace_back();
        for (auto fanin : tran_fanout->FaninGates()) {
          possilbe_good.back().emplace_back(fanin, LOGIC_x);
        }
        if (tran_fanout->GetGType() == GType::G_DFF) {
          possilbe_good.back().emplace_back(tran_fanout, LOGIC_x);
        }
      }

      if (curr_bad == LOGIC_x) {
        possilbe_bad = driven_table->possible_cubes(
            tran_fanout, BAD_VALUE[obj_val], fanin_bad, gate2scanchainid,
            is_falling_edge_dff, no_assign);
      } else {
        possilbe_bad.emplace_back();
        for (auto fanin : tran_fanout->FaninGates()) {
          possilbe_bad.back().emplace_back(fanin, LOGIC_x);
        }
        if (tran_fanout->GetGType() == GType::G_DFF) {
          possilbe_bad.back().emplace_back(tran_fanout, LOGIC_x);
        }
      }

      // Combine possible good and bad values.
      // If combination is conflict with current value assignments,
      // reject the combination.
      for (int i = 0; i < possilbe_good.size(); i++) {
        for (int j = 0; j < possilbe_bad.size(); j++) {
          bool conflict = false;
          path_justify_[p].emplace_back();
          for (int k = 0; k < tran_fanout->FaninSize(); k++) {
            // If combination is impossible for sensitizing
            // current propagation gate.
            Gate *fanin = tran_fanout->FaninGates()[k];
            if (k == prop_idx &&
                ((deter_val(good[fault_path->GetGId()]) &&
                  deter_val(possilbe_good[i][k].second) &&
                  good[fault_path->GetGId()] != possilbe_good[i][k].second) ||
                 (deter_val(bad[fault_path->GetGId()]) &&
                  deter_val(possilbe_bad[j][k].second) &&
                  bad[fault_path->GetGId()] != possilbe_bad[j][k].second))) {
              conflict = true;
              break;
            }
            if (fault_cone[fanin->GetGId()] != atpg_invokes &&
                faulty_val(NINE_VALUE[possilbe_good[i][k].second]
                                     [possilbe_bad[j][k].second])) {
              conflict = true;
              break;
            }

            assert(tran_fanout->FaninGates()[k] == possilbe_good[i][k].first &&
                   tran_fanout->FaninGates()[k] == possilbe_bad[j][k].first);

            path_justify_[p].back().emplace_back(
                tran_fanout->FaninGates()[k],
                NINE_VALUE[possilbe_good[i][k].second]
                          [possilbe_bad[j][k].second]);
          }
          if (tran_fanout->GetGType() == GType::G_DFF) {
            if (deter_val(possilbe_good[i][4].second) &&
                deter_val(possilbe_bad[j][4].second) &&
                possilbe_good[i][4].second != possilbe_bad[j][4].second) {
              conflict = true;
            } else {
              path_justify_[p].back().emplace_back(
                  tran_fanout, NINE_VALUE[possilbe_good[i][4].second]
                                         [possilbe_bad[j][4].second]);
            }
          }

          if (conflict) {
            path_justify_[p].pop_back();
          }
        }
      }
    }

    max_dec_ += static_cast<int>(path_justify_[p].size());
  }
}

/**
 * @brief
 * connect constructed children nodes to parent node
 * @param root parent node
 * @param childrn children nodes
 */
void SplitDecisionTree::insert_dec(
    SplitSiblingTreeNode *root,
    const std::vector<SplitSiblingTreeNode *> &children) {
  if (children.empty())
    return;

  SplitSiblingTreeNode *sibling;
  if (!root->child_) {
    root->child_ = children[0];
    sibling = children[0];
  } else {
    auto child = root->child_;
    while (child->sibling_) {
      child = child->sibling_;
    }
    child->sibling_ = children[0];
    sibling = children[0];
  }

  for (int i = 1; i < children.size(); i++) {
    sibling->sibling_ = children[i];
    sibling = children[i];
  }

  //  for (auto child : children) {
  //    if (child->dec_type() == GOOD_JUSTIFY ||
  //        child->dec_type() == BAD_JUSTIFY) {
  //      dec_picker_->addElement(child,
  //      testability_SCOAP_[child->gate_->GetGId()][child->val_]);
  //    }
  //  }
}

/**
 * @brief
 * delete child nodes recursively
 * @param root parent node
 */
void SplitDecisionTree::delete_silb_child(SplitSiblingTreeNode *root) {
  std::queue<SplitSiblingTreeNode *> que;
  que.push(root);

  while (!que.empty()) {
    SplitSiblingTreeNode *node = que.front();
    que.pop();

    SplitSiblingTreeNode *t_node = node;

    while (t_node) {
      node = t_node;
      if (node->child_) {
        que.push(node->child_);
      }

      t_node = node->sibling_;

      node->child_ = nullptr;
      node->sibling_ = nullptr;
      delete node;
    }
  }
}

/**
 * @brief
 * find next decision node in decision tree
 * @details
 * if all decision nodes are already done, return `nullptr`
 * @return
 * next decision node
 */
SplitSiblingTreeNode *SplitDecisionTree::next_dec() {
  SplitSiblingTreeNode *parent = tg_;
  SplitSiblingTreeNode *target = nullptr;

  std::stack<SplitSiblingTreeNode *> stk;
  stk.push(parent);

  while (!stk.empty()) {
    parent = stk.top();
    stk.pop();

    // If this node is not finished,
    // return this node.
    if (!parent->done()) {
      target = parent;
      break;
    }

    if (parent->sibling_) {
      stk.push(parent->sibling_);
    }
    if (parent->child_) {
      stk.push(parent->child_);
    }
  }

  return target;
}

} // namespace ictest
